{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "76978731",
   "metadata": {},
   "source": [
    "## Recipe of the Day Agent\n",
    "\n",
    "The Recipe of the Day Agent is an orchestrated AI workflow designed to automatically generate, filter, enrich, and deliver the detailed recipe to users via email. It leverages multiple specialized sub-agents (or “tools”) and handoffs coordinated by a central Orchestrator Agent.\n",
    "\n",
    "### Prerequisites:\n",
    "\n",
    "1. You need a Gemini API key. This key should be set as an environment variable named GEMINI_API_KEY. \n",
    "2. You need a Sendgrid API key. This key should be set as an environment variable named SENDGRID_API_KEY. \n",
    "3. Save the sender email id and the receiver email id in SENDER_EMAIL_ID and RECEIVER_EMAIL_ID environment variables.\n",
    "\n",
    "### Recipe Orchestrator Agent workflow:\n",
    "\n",
    "1️⃣ Recipe Ideation Tool\n",
    "- Generates 5 diverse recipe ideas\n",
    "- Returns a Pydantic-based RecipeList model\n",
    "\n",
    "2️⃣ Recipe Filter & Selector Agent\n",
    "- Applies selection rules\n",
    "- Chooses one recipe from the list\n",
    "\n",
    "3️⃣ Recipe Details Agent\n",
    "- Expands the selected recipe\n",
    "- Adds ingredients, steps, cook time, etc.\n",
    "\n",
    "4️⃣ Emailer Agent\n",
    "- Generates email subject & HTML body\n",
    "- Uses SendGrid API to deliver the email\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "1d6df8e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "from agents import Agent, Runner, trace, function_tool, OpenAIChatCompletionsModel\n",
    "import sendgrid\n",
    "from sendgrid.helpers.mail import Mail, Email, To, Content\n",
    "import os\n",
    "import asyncio\n",
    "from openai import AsyncOpenAI\n",
    "from pydantic import BaseModel\n",
    "from typing import Literal, List, Dict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "642412b0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "load_dotenv(override=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "a1373f48",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OpenAI API Key exists and begins sk-proj-\n",
      "Google API Key exists and begins AI\n",
      "Sendgrid API Key exists and begins SG.\n"
     ]
    }
   ],
   "source": [
    "openai_api_key = os.getenv('OPENAI_API_KEY')\n",
    "google_api_key = os.getenv('GOOGLE_API_KEY')\n",
    "sendgrid_api_key = os.getenv('SENDGRID_API_KEY')\n",
    "\n",
    "if openai_api_key:\n",
    "    print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
    "else:\n",
    "    print(\"OpenAI API Key not set\")\n",
    "\n",
    "if google_api_key:\n",
    "    print(f\"Google API Key exists and begins {google_api_key[:2]}\")\n",
    "else:\n",
    "    print(\"Google API Key not set (and this is optional)\")\n",
    "\n",
    "if sendgrid_api_key:\n",
    "    print(f\"Sendgrid API Key exists and begins {sendgrid_api_key[:3]}\")\n",
    "else:\n",
    "    print(\"Sendgrid API Key not set (and this is optional)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5b1ede9",
   "metadata": {},
   "outputs": [],
   "source": [
    "GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
    "gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)\n",
    "gemini_model = OpenAIChatCompletionsModel(model=\"gemini-2.0-flash\", openai_client=gemini_client)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "6ebc85f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Recipe(BaseModel):\n",
    "    title: str\n",
    "    cuisine: Literal[\"Indian\", \"Italian\", \"Mexican\", \"British\"]\n",
    "    protein_source: str\n",
    "    max_cook_time_minutes: int\n",
    "    brief_description: str\n",
    "    estimated_protein_gms: int\n",
    "\n",
    "class RecipeList(BaseModel):\n",
    "    recipes: List[Recipe]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "47246549",
   "metadata": {},
   "outputs": [],
   "source": [
    "instructions = \"\"\"Follow below step carefully:\n",
    "    1. MUST Generate a list of 5 diverse recipes.\n",
    "    2. Each recipe MUST be vegeterian, protein-rich and be easy-to-cook. Strict adherence to the provided schema is required.\n",
    "    3. Handoff all the generated recipes to the 'Recipe Filter and Selector' agent. The Recipe Filter and Selector will take care of filtering and selecting.\n",
    "    Crucial Rules:\n",
    "- You must generate the recipes first yourself and only then hand off to the 'Recipe Filter and Selector' agent\"\"\"\n",
    "\n",
    "recipe_ideation_agent = Agent(\n",
    "    name=\"Recipe Ideation Agent\",\n",
    "    instructions=instructions,\n",
    "    model=\"gpt-4o-mini\",\n",
    "    output_type=RecipeList)\n",
    "\n",
    "recipe_ideation_tool = recipe_ideation_agent.as_tool(\n",
    "    tool_name=\"recipe_ideation_agent\",\n",
    "    tool_description=\"Recipe ideation agent tool\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "334cec6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "filter_selection_instructions = \"\"\"You are a recipe filtering and selecting agent.\n",
    "    You receive the list of recipes as input.\n",
    "    \n",
    "    Your job is to filter recipes according to the following criteria:\n",
    "    1. Must be vegetarian\n",
    "    2. Must be protein-rich (≥ 15 grams of protein per serving)\n",
    "    3. Must be under 30 minutes preparation time\n",
    "    4. Must be Indian, Italian, Mexican or British cuisine\n",
    "\n",
    "    From the filtered recipes, select 1 best recipe based on:\n",
    "    1. Highest protein content\n",
    "    2. Variety of cuisines\n",
    "    3. Balanced ingredient list\n",
    "\"\"\"\n",
    "\n",
    "recipe_filter_selector_agent = Agent(\n",
    "    name=\"Recipe Filter and Selector\",\n",
    "    instructions=filter_selection_instructions,\n",
    "    model=gemini_model,\n",
    "    output_type=Recipe\n",
    ")\n",
    "\n",
    "recipe_filter_selector_tool = recipe_filter_selector_agent.as_tool(\n",
    "    tool_name=\"recipe_filter_selector_agent\",\n",
    "    tool_description=\"Recipe Filter And Selector Tool\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "41af1afe",
   "metadata": {},
   "outputs": [],
   "source": [
    "details_instructions = \"\"\"You are a recipe details agent.\n",
    "    You receive a recipe as input.\n",
    "    \n",
    "    Your job is to find the details of the recipe including instructions to cook, ingredients etc.\n",
    "\n",
    "    Your final message should include:\n",
    "    - **Title**: Name of the recipe\n",
    "    - **Cuisine / Theme**\n",
    "    - **Meal Type** (e.g. breakfast, lunch, dinner, dessert)\n",
    "    - **Ingredients**\n",
    "    - **Instructions**\n",
    "    - **Nutrition Info**\n",
    "    - **Shopping List**\n",
    "    - **Chef’s Note**: one friendly line or tip from you as the orchestrator\n",
    "\"\"\"\n",
    "\n",
    "recipe_details_agent = Agent(\n",
    "    name=\"Recipe Details Agent\",\n",
    "    instructions=details_instructions,\n",
    "    model=\"gpt-4o-mini\")\n",
    "\n",
    "recipe_details_tool = recipe_details_agent.as_tool(\n",
    "    tool_name=\"recipe_details_agent\",\n",
    "    tool_description=\"Recipe details agent tool\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "969e0cce",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "a4dc7477",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "subject_instructions = \"You are a subject line writer for a recipe email. \\\n",
    "You write a subject line for a recipe email that is engaging and relevant to the recipe.\"\n",
    "\n",
    "subject_agent = Agent(\n",
    "    name=\"Subject Line Writer\",\n",
    "    instructions=subject_instructions,\n",
    "    model=gemini_model)\n",
    "\n",
    "subject_tool = subject_agent.as_tool(\"subject_agent\", \"Write a subject line for a recipe email\")\n",
    "\n",
    "html_instructions = \"You can convert a text email body to an HTML email body. \\\n",
    "You are given a text email body which might have some markdown \\\n",
    "and you need to convert it to an HTML email body with simple, clear, compelling layout and design.\"\n",
    "\n",
    "html_agent = Agent(\n",
    "    name=\"HTML Converter\",\n",
    "    instructions=html_instructions,\n",
    "    model=gemini_model)\n",
    "\n",
    "html_tool = html_agent.as_tool(\"html_agent\", \"Convert a text email body to an HTML email body\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b2f1ce4",
   "metadata": {},
   "outputs": [],
   "source": [
    "sender_email_id=os.getenv('SENDER_EMAIL_ID')\n",
    "receiver_email_id=os.getenv('RECEIVER_EMAIL_ID')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a1e063d",
   "metadata": {},
   "outputs": [],
   "source": [
    "@function_tool\n",
    "def send_html_email(subject: str, html_body: str) -> Dict[str, str]:\n",
    "    \"\"\" Send out an email with the given subject and HTML body to all sales prospects \"\"\"\n",
    "    sg = sendgrid.SendGridAPIClient(api_key=sendgrid_api_key)\n",
    "    from_email = Email(sender_email_id)  # Change to your verified sender\n",
    "    to_email = To(receiver_email_id)  # Change to your recipient\n",
    "    content = Content(\"text/html\", html_body)\n",
    "    mail = Mail(from_email, to_email, subject, content).get()\n",
    "    sg.client.mail.send.post(request_body=mail)\n",
    "    return {\"status\": \"success\"}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "3c8da903",
   "metadata": {},
   "outputs": [],
   "source": [
    "tools = [subject_tool, html_tool, send_html_email]\n",
    "\n",
    "emailer_instructions =\"You are an email formatter and sender. You receive the body of an email to be sent. \\\n",
    "You first use the subject_writer tool to write a subject for the email, then use the html_converter tool to convert the body to HTML. \\\n",
    "Finally, you use the send_html_email tool to send the email with the subject and HTML body.\"\n",
    "\n",
    "emailer_agent = Agent(\n",
    "    name=\"Email Manager\",\n",
    "    model=gemini_model,\n",
    "    instructions = emailer_instructions,\n",
    "    tools = tools\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b9414c14",
   "metadata": {},
   "outputs": [],
   "source": [
    "orchestrator_instructions = \"\"\" \n",
    "You are the **Recipe of the Day Orchestrator**, a culinary conductor that coordinates several expert cooking agentsand return a single \"Recipe of the Day\".\n",
    "\n",
    "### Your mission\n",
    "Plan and deliver a complete recipe of the day by:\n",
    "1. Calling the Recipe ideation agent to provide the list of recipes\n",
    "2. Call the Recipe filter and selector agent passing all the recipe to produce one recipe.\n",
    "3. Call the Recipe details tool passing single recipe to product recipe details.\n",
    "4. Handoff for Sending: Pass ONLY the selected recipe to the 'Email Manager' agent. The Email Manager will take care of formatting and sending.\n",
    "\n",
    "### Rules\n",
    "- Use sub-agents rather than making up all details yourself.\n",
    "\n",
    "### Tone\n",
    "Warm, creative, and professional — like a magazine feature.\n",
    "\"\"\"\n",
    "\n",
    "recipe_of_a_day = Agent(\n",
    "    name=\"Recipe of a day\", \n",
    "    instructions=orchestrator_instructions,\n",
    "    tools=[recipe_ideation_tool, recipe_filter_selector_tool, recipe_details_tool],\n",
    "    handoffs=[emailer_agent],\n",
    "    model=gemini_model)\n",
    "\n",
    "message = \"Generate recipe of a day\"\n",
    "\n",
    "with trace(\"Recipe of a day\"):\n",
    "    result = await Runner.run(recipe_of_a_day, message)\n",
    "\n",
    "print(result.final_output)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
